﻿using System;
using System.Collections.Generic;
using System.Net;
using Moq;
using NUnit.Framework;
using OpenWaves.ActivityFeed.Web;
using OpenWaves.ActivityFeed.Web.Handler;
using OpenWaves.ActivityFeed.Web.Modules;
using System.Linq;

namespace OpenWaves.ActivityFeed.UnitTests.Web
{
    [TestFixture]
    public class UpdatesModuleTests
    {
        [SetUp]
        public void InitializeActivityFeedBuilderFactory()
        {
            var basicResolver = new BasicResolver();
            basicResolver.Register<IActivityFeedBuilderFactory>(GetActivityFeedBuilderFactory());
            ServiceLocator.SetResolver(basicResolver);
        }

        [TestCase("GET", "/activityFeed/updates")]
        [TestCase("POST", "/activityFeed/updates")]
        [TestCase("DELETE", "/activityFeed/updates/123")]
        public void When_user_is_not_authenticated_Then_routes_return_Unauthorized(string verb, string route)
        {
            var apiHandler = new ActivityFeedApiHandler(IdentityProviderMock.WithAnonymousUser(), 
                new UpdatesModule(
                    IdentityProviderMock.WithAnonymousUser(),
                    new Mock<IActivityFeedService>().Object, 
                    UserGroupsProviderMock.WithNoGroups(),
                    GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(apiHandler);

            var response = browser.MakeRequest(verb, route);

            Assert.That(response.StatusCode, Is.EqualTo((int)HttpStatusCode.Unauthorized));
        }

        [Test]
        public void Given_authenticated_user_GET_updates_returns_list_of_updates()
        {
            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);
            activityFeedServiceMock.Setup(service => service.GetUpdates()).Returns(DummyUpdate.From("john").ToQueryableList());
            activityFeedServiceMock.Setup(service => service.GetSubscriber("john")).Returns(new Subscriber("john"));

            var userIdentityProvider = IdentityProviderMock.With("john");
            var activityFeedApiHandler = new ActivityFeedApiHandler(userIdentityProvider,
                new UpdatesModule(
                    userIdentityProvider, 
                    activityFeedServiceMock.Object,
                    UserGroupsProviderMock.WithNoGroups(), GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(activityFeedApiHandler);
            var result = browser.Get("/activityFeed/updates").DeserializeBody<Result<List<UpdateDto>>>();

            Assert.That(result.Status, Is.EqualTo("ok"));
            Assert.That(result.Value, Is.Not.Empty);
        }

        [Test]
        public void Given_authenticated_user_GET_updates_with_pagination_parameters_returns_subset_of_updates()
        {
            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);

            var updates = new List<Update>();
            using (var fakeTime = FakeTimeScope.Begin())
            {
                fakeTime.Now = new DateTime(2000, 1, 1);
                updates.Add(DummyUpdate.From("john"));

                fakeTime.Now = new DateTime(2000, 1, 2);
                updates.Add(DummyUpdate.From("john"));
                    
                fakeTime.Now = new DateTime(2000, 1, 3);
                updates.Add(DummyUpdate.From("john"));
            }

            activityFeedServiceMock.Setup(service => service.GetUpdates()).Returns(updates.AsQueryable());
            activityFeedServiceMock.Setup(service => service.GetSubscriber("john")).Returns(new Subscriber("john"));

            var userIdentityProvider = IdentityProviderMock.With("john");
            var activityFeedApiHandler = new ActivityFeedApiHandler(userIdentityProvider,
                new UpdatesModule(
                    userIdentityProvider,
                    activityFeedServiceMock.Object,
                    UserGroupsProviderMock.WithNoGroups(), GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(activityFeedApiHandler);
                        


            var result = browser.Get("/activityFeed/updates?dateFrom=2000-01-02T00%3A00%3A00.000Z&pageSize=1").DeserializeBody<Result<List<UpdateDto>>>();

            Assert.That(result.Status, Is.EqualTo("ok"));
            Assert.That(result.Value.Count, Is.EqualTo(1));
            Assert.That(result.Value.Single().Id, Is.EqualTo(updates[1].Id));
        }

        [Test]
        public void Given_authenticated_user_GET_updates_with_searchTerm_parameter_then_searchTerm_is_passed_to_service()
        {
            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);

            var dummyUpdate  = DummyUpdate.From("john");
            var searchTerm = ((DummyUpdateContent)(dummyUpdate.Content)).Text;

            var updates = new List<Update>();
            using (var fakeTime = FakeTimeScope.Begin())
            {
                fakeTime.Now = new DateTime(2000, 1, 1);
                updates.Add(dummyUpdate);

                fakeTime.Now = new DateTime(2000, 1, 2);
                updates.Add(DummyUpdate.From("john"));

                fakeTime.Now = new DateTime(2000, 1, 3);
                updates.Add(DummyUpdate.From("john"));
            }

            activityFeedServiceMock.Setup(service => service.SearchUpdates(searchTerm)).Returns(updates.AsQueryable());
            activityFeedServiceMock.Setup(service => service.GetSubscriber("john")).Returns(new Subscriber("john"));

            var userIdentityProvider = IdentityProviderMock.With("john");
            var activityFeedApiHandler = new ActivityFeedApiHandler(userIdentityProvider,
                new UpdatesModule(
                    userIdentityProvider,
                    activityFeedServiceMock.Object,
                    UserGroupsProviderMock.WithNoGroups(),
                    GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(activityFeedApiHandler);

            var result = browser.Get("/activityFeed/updates?searchTerm=" + Url.Encode(searchTerm)).DeserializeBody<Result<List<UpdateDto>>>();

            Assert.That(result.Status, Is.EqualTo("ok"));
        }

        [Test]
        public void Given_authenticated_user_POST_to_updates_publishes_new_update_as_the_user()
        {
            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);
            activityFeedServiceMock.Setup(service => service.PublishUpdate(
                new Post("StatusUpdateText"),
                "maciek", "web",
                Topics.Containing(Topic.FromUserId("maciek")), true, It.IsAny<IEnumerable<UserGroup>>())).Returns(DummyUpdate.From("111111"));

            var userIdentityProvider = IdentityProviderMock.With("maciek");
            var apiHandler = new ActivityFeedApiHandler(userIdentityProvider, 
                new UpdatesModule(
                    userIdentityProvider, 
                    activityFeedServiceMock.Object, 
                    UserGroupsProviderMock.WithNoGroups(),
                    GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(apiHandler);

            browser.PostJson("/activityFeed/updates", new { text = "StatusUpdateText", source = "web" });

            activityFeedServiceMock.VerifyAll();
        }

        [Test]
        public void Given_authentiocated_user_attempt_to_DELETE_update_by_another_user_returns_Forbidden()
        {
            const string updateId = "update/123";

            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);
            activityFeedServiceMock.Setup(service => service.GetUpdate(updateId)).Returns(DummyUpdate.From("john"));

            var userIdentityProvider = IdentityProviderMock.With("sam");
            var apiHandler = new ActivityFeedApiHandler(userIdentityProvider, 
                new UpdatesModule(
                    userIdentityProvider, 
                    activityFeedServiceMock.Object, 
                    UserGroupsProviderMock.WithNoGroups(),
                    GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(apiHandler);
            var response = browser.Delete("/activityFeed/updates/" + Url.Encode(updateId));

            Assert.That(response.StatusCode, Is.EqualTo((int)HttpStatusCode.Forbidden));
        }

        [Test]
        public void Given_update_by_user_DELETE_update_by_publisher_removes_update()
        {
            const string updateId = "update/123";
            var update = DummyUpdate.From("john");

            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);
            activityFeedServiceMock.Setup(service => service.GetUpdate(updateId)).Returns(update);
            activityFeedServiceMock.Setup(service => service.RemoveUpdate(update));

            var userIdentityProvider = IdentityProviderMock.With("john");
            var apiHandler = new ActivityFeedApiHandler(userIdentityProvider, 
                new UpdatesModule(
                    userIdentityProvider, 
                    activityFeedServiceMock.Object, 
                    UserGroupsProviderMock.WithNoGroups(),
                    GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(apiHandler);
            browser.Delete("/activityFeed/updates/" + Url.Encode(updateId));

            activityFeedServiceMock.VerifyAll();
        }


        [Test]
        public void Given_authenticated_user_POST_to_updates_in_context_of_topic_publishes_update_on_the_topics_as_the_user()
        {
            const string topic = "elo-melo";
            const string topic2 = "topic-2";

            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);
            
            activityFeedServiceMock.Setup(
                service =>
                service.PublishUpdate(new Post("text test"), "john", "web", Topics.ContainingAll(new Topic(topic), new Topic(topic2), Topic.FromUserId("john")),
                true, Enumerable.Empty<UserGroup>())).Returns(DummyUpdate.From("user1111"));

            var userInfoProviderMock = new Mock<IUserProfileProvider>();
            userInfoProviderMock.Setup(provider => provider.GetUserProfile("user1111")).Returns(new FakeUserInfo
            {
                Name = "user1111"
            });

            var userIdentityProvider = IdentityProviderMock.With("john");
            var apiHandler = new ActivityFeedApiHandler(userIdentityProvider, 
                new UpdatesModule(
                    userIdentityProvider, 
                    activityFeedServiceMock.Object, 
                    UserGroupsProviderMock.WithNoGroups(),
                    GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(apiHandler);
            browser.PostJson("/activityFeed/updates", new { text = "text test", source = "web", newTopics = new Topic[] { new Topic(topic), new Topic(topic2) } });

            activityFeedServiceMock.VerifyAll();
        }

        [Test]
        public void Given_authenticated_user_When_GetUpdates_Then_return_updates_limited_to_his_roles()
        {
            var updates = new List<Update>();

            using (var fakeTime = FakeTimeScope.Begin(DateTime.UtcNow.AddYears(-1)))
            {
                updates.Add(new Update(new Post("test"), "user1", "source", new[] { new Topic("t1") }, false, new[] { new UserGroup("administator") }));
                fakeTime.Now = fakeTime.Now.AddDays(1);
                updates.Add(new Update(new Post("test2"), "user1", "source", new[] { new Topic("t1") }, false, new[] { new UserGroup("employee") }));
                fakeTime.Now = fakeTime.Now.AddDays(1);
                updates.Add(new Update(new Post("test3"), "user1", "source", new[] { new Topic("t1") }, false, new[] { new UserGroup("manager") }));
                fakeTime.Now = fakeTime.Now.AddDays(1);
                updates.Add(new Update(new Post("test4"), "userToCheck", "source", new[] { new Topic("t1") }, false, new[] { new UserGroup("employee") }));
            }

            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);
            activityFeedServiceMock.Setup(service =>service.GetUpdates()).Returns(updates.AsQueryable());
            activityFeedServiceMock.Setup(service => service.GetSubscriber("john")).Returns(new Subscriber("john"));

            var userIdentityProvider = IdentityProviderMock.With("john");
            var apiHandler = new ActivityFeedApiHandler(userIdentityProvider, 
                new UpdatesModule(
                    userIdentityProvider, 
                    activityFeedServiceMock.Object, 
                    UserGroupsProviderMock.With(new UserGroup("employee")),
                    GetUpdateDtoBuilderMockObect()));

            var browser = new Browser(apiHandler);
            var result = browser.Get("/activityFeed/updates?feedType=global").DeserializeBody<Result<List<UpdateDto>>>();

            Assert.That(result.Status, Is.EqualTo("ok"));
            Assert.That(result.Value, Is.Not.Empty);
            Assert.That(result.Value.Count, Is.EqualTo(2));
            Assert.That(result.Value[1].Publisher.Id, Is.EqualTo("userToCheck"));
        }

        [Test]
        public void When_GET_updates_filteredByTopic_Then_topics_are_passed_to_service()
        {
            var activityFeedServiceMock = new Mock<IActivityFeedService>(MockBehavior.Strict);
            activityFeedServiceMock.Setup(service => service.GetSubscriber("john")).Returns(new Subscriber("john"));
            activityFeedServiceMock.Setup(service => service.GetUpdates()).Returns(DummyUpdate.From("john").ToQueryableList());

            var userIdentityProvider = IdentityProviderMock.With("john");
            var apiHandler = new ActivityFeedApiHandler(userIdentityProvider,
                new UpdatesModule(
                    userIdentityProvider,
                    activityFeedServiceMock.Object,
                    UserGroupsProviderMock.WithNoGroups(),
                    GetUpdateDtoBuilderMockObect()));

            var topics = Url.Encode("topic1;topic2");

            var browser = new Browser(apiHandler);
            browser.Get("/activityFeed/updates?topicsFilter=" + topics);

            activityFeedServiceMock.VerifyAll();
        }

        private static IUpdateDtoBuilder GetUpdateDtoBuilderMockObect()
        {
            var userProfileProviderMock = new Mock<IUserProfileProvider>();
            userProfileProviderMock.Setup(p => p.GetUserProfile(It.IsAny<string>())).Returns(new object());
            return new UpdateDtoBuilder(userProfileProviderMock.Object);
        }

        private static IActivityFeedBuilderFactory GetActivityFeedBuilderFactory()
        {
            return new ActivityFeedBuilderFactory();
        }
    }


    public static class Topics
    {
        public static IEnumerable<Topic> Containing(Topic topic)
        {
            return It.Is<IEnumerable<Topic>>(t => t.Contains(topic));
        }

        public static IEnumerable<Topic> ContainingAll(params Topic[] expectedTopics)
        {
            return It.Is<IEnumerable<Topic>>(topics => expectedTopics.All(topics.Contains));
        }

        public static IEnumerable<Topic> ContainingOnly(Topic topic)
        {
            return It.Is<IEnumerable<Topic>>(topics => topics.Count() == 1 && topics.First().Equals(topic));
        }
    }
}